#ifndef __ZTOUCHTCPSERVER_H
#define __ZTOUCHTCPSERVER_H

#include <thread>
#include <ctime>
#include "rbtree.h"
#include "ZTouchProto.h"

#define _DEBUG

using namespace std;
using namespace datastructureZ;

namespace ZTouch
{

class ZTouchTCPServer
{
	//public:typedef list<pair<socket_t, long>> LoginPool;
	//						 connect, deadTime
	public:struct ServerInfo
	{
		socket_t	socketID;
		sockaddr_in serverAddr;
		int			serAddrLen;
		bool		started;
		
		ServerInfo(void):socketID(-1), serAddrLen(0),started(false){}
		
		bool startup(void)
		{
			if(socketID != -1  && serAddrLen != 0) return started = true;
			else return false;
		}
		
		void termi(void)
		{
			started = false;
		}
	};
	
	private:class UsersManager
	{
		friend class ZTouchTCPServer;
		
		public:typedef RBTree<UserInfo> UserMap;
		//--------------------------------------------
		private:mutex		readLock;
		private:mutex		writeLock;
		//private:UserList	users;
		private:UserMap		users;
		//--------------------------------------------
		
		//仅读锁上锁时，任何多个线程都可以进行读取，但不能进行写入
		//写锁上锁时，任何线程都不能读取，只有上锁线程能够进行写入
		//读取时仅上写锁(读锁上锁一次后立即释放)，写入时上双锁 
		#define CRITICAL_WRITE(ptr_rlock, ptr_wlock) (*(ptr_rlock)).lock();(*(ptr_wlock)).lock();
		#define END_CRITICAL_WRITE(ptr_rlock, ptr_wlock) (*(ptr_wlock)).unlock();(*(ptr_rlock)).unlock();
		
		#define CRITICAL_READ(ptr_rlock, ptr_wlock) {\
			/*确保没有线程在写入 */\
			(*(ptr_rlock)).lock();\
			/*确保写锁上锁，而不因写锁阻塞*/\
			bool wlockedByMe = (*(ptr_wlock)).try_lock();\
			/*立即释放读锁*/\
			(*(ptr_rlock)).unlock();
		
		#define END_CRITICAL_READ(ptr_rlock, ptr_wlock) \
			if(wlockedByMe) (*(ptr_wlock)).unlock();\
		}
		
		
		private:static void userSession(const struct UserInfo userInfo, UserMap* users, mutex* rLock, mutex* wLock)
		{
			/*
			recv timeout
			if timeout kick
			else work
			goto start
			*/
			
			/*
			client msg send:
				1.send ZTouchDataHead
				2.send ZTouchMsg + content
			*/
			
			UserInfo tmpSearch;
			//int recvLength = 0;
			ZTouchDataHead head;
			
			for(;;)
			{
				head.clean();
				bool suc = ZMessage::getHead(userInfo.connect, head, MSG_WAITALL);
				#ifdef _DEBUG
					cout<<"msgLength = "<<head.getLength()<<", type = "<<head.getType()<<", suc = "<<suc<<endl;
				#endif
				if(
					!suc || 
					!(
						head.getType() == ZTouchDataHead::KEEP || 
						head.getType() == ZTouchDataHead::CHATING
					) ||
					(
						head.getType() == ZTouchDataHead::CHATING &&
						head.getLength() < (int)sizeof(struct ZTouchMsg)
					)
				)
				{
					head.setLength(0);
					head.setType(ZTouchDataHead::TERMI);
					ZMessage::putHead(userInfo.connect, head, MSG_WAITALL);
					
					CRITICAL_WRITE(rLock, wLock);
						users->remove(userInfo);
					END_CRITICAL_WRITE(rLock, wLock);
					
					close(userInfo.connect);
					
					cout<<userInfo.id<<" has been kicked"<<endl;
					
					break;
				}
				
				if(head.getType() == ZTouchDataHead::KEEP) continue;
				
				//CHATING
				#ifdef _DEBUG
					cout<<"recv chating from "<< userInfo.id <<endl;
				#endif
				
				/*
				ZTouchMsg *msg = (ZTouchMsg*) malloc(head.getLength());
				#ifdef _DEBUG
					cout<<"ready to recv"<<endl;
				#endif
				recvLength = recv(userInfo.connect, &msg, head.getLength(), MSG_WAITALL);
				#ifdef _DEBUG
					cout<<"recved"<<endl;
				#endif
				*/
				
				ZMessage message;
				if(!message.load(userInfo.connect, head, MSG_WAITALL))
				{
					#ifdef _DEBUG
						cout<<"load fail"<<endl;
					#endif
					CRITICAL_WRITE(rLock, wLock);
						users->remove(userInfo);
					#ifdef _DEBUG
						cout<<"unlock"<<endl;
					#endif
					END_CRITICAL_WRITE(rLock, wLock);
					#ifdef _DEBUG
						cout<<"unlocked"<<endl;
					#endif
					close(userInfo.connect);
					break;
				}
				
				//search target user in users
				#ifdef _DEBUG
					cout<<"creating id"<<endl;
				#endif
				tmpSearch.id = string(message.getDesName());
				#ifdef _DEBUG
					cout<<"id created"<<endl;
				#endif
				#ifdef _DEBUG
					cout<<"dest:"<<tmpSearch.id<<"-"<<endl;
					cout<<"content:"<<string(message.getContent())<<endl;
				#endif
				const UserInfo* desUser = nullptr;
				/*****/
				CRITICAL_READ(rLock, wLock);
				/*****/
					desUser = users->search(tmpSearch);
				/*****/
				END_CRITICAL_READ(rLock, wLock);
				/*****/
				if(desUser == nullptr)
				{//des-user not found, tell client.
					#ifdef _DEBUG
						cout<<"dest user find fail"<<endl;
					#endif
					head.setLength(0);
					head.setType(ZTouchDataHead::DESNOTFOUND);
					ZMessage::putHead(userInfo.connect, head, MSG_WAITALL);
				}
				else
				{//succeed, send
					#ifdef _DEBUG
						cout<<"dest user find succeed"<<endl;
					#endif
					head.setLength(0);
					if(message.sendTo(desUser->connect, MSG_WAITALL)) head.setType(ZTouchDataHead::SENDSUCCEED);
					else head.setType(ZTouchDataHead::SENDFAIL);
					ZMessage::putHead(userInfo.connect, head, MSG_WAITALL);
				}
			}
		}
		
		public:bool login(socket_t _connect, string userName)
		{
			bool succeed = false;
			UserInfo newUserInfo = {userName, _connect};
			CRITICAL_WRITE(&readLock, &writeLock);
				succeed = users.insert(newUserInfo);
			END_CRITICAL_WRITE(&readLock, &writeLock);
			if(!succeed) return false;
			
			thread* newSession = new thread(userSession, newUserInfo, &users, &readLock, &writeLock);
			
			return true;
		}
		
		public:bool kick(string userName)
		{
			UserMap::Node* targetNode = nullptr;
			ZTouchDataHead head;
			UserInfo targetUserInfo = {userName, 0};
			const UserInfo *got = nullptr;
			
			CRITICAL_READ(&readLock, &writeLock);
				targetNode = users.searchNode(targetUserInfo);
			END_CRITICAL_READ(&readLock, &writeLock);
			
			got = users.getNodeData(targetNode);
			if(got != nullptr)
			{
				head.setLength(0);
				head.setType(ZTouchDataHead::LOGOUT);
				ZMessage::putHead(got->connect, head, MSG_WAITALL);
			}
			
			CRITICAL_WRITE(&readLock, &writeLock);
				targetNode = users._remove(targetNode);
			END_CRITICAL_WRITE(&readLock, &writeLock);
			
			if(targetNode != nullptr)
			{
				delete(targetNode);
				return true;
			}
			else return false;
		}
		
		public:void getUserList_seri(string& data)
		{
			bool first = true;
			for
			(
				UserMap::iterator i = users.begin();
				i != users.end();
				++i
			)
			{
				if(!first) data += '\n';
				data += i->id;
			}
		} 
	};
	
	class ServerController
	{
		private:UsersManager&	userMan;
		private:socket_t		connect;
		
		public:ServerController(short _port, short _maxWaitQue, UsersManager& userManager): userMan(userManager), connect(0)
		{
			sockaddr_in controllerAddr;
			connect							= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
			controllerAddr.sin_family		= AF_INET;
			controllerAddr.sin_addr.s_addr	= htonl(INADDR_ANY);
			controllerAddr.sin_port			= htons(_port);
			
			memset(controllerAddr.sin_zero, 0, 8);
			bind(connect, (struct sockaddr*)&controllerAddr, sizeof(controllerAddr));
			
			if(listen(connect, _maxWaitQue) != 0)
			{
				throw("Server controller listen fail.");
			}
			
		}
			
		public:void operator() (void)
		{
			
		} 
	};
	
	friend class UsersManager;
	
	//--------------------------------------------
	private:ServerInfo			info;
	private:UsersManager		usersMan;
	private:thread*				listenWorking;
	private:struct timeval		unloginLiveTime;
	//--------------------------------------------
	
	public:ZTouchTCPServer(long _loginTime = 30, short _port = 32777, short _conntrollerPort = 32799, short _maxWaitQue = 1024):listenWorking(nullptr)
	{
		info.socketID					= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		info.serverAddr.sin_family		= AF_INET;
		info.serverAddr.sin_addr.s_addr	= htonl(INADDR_ANY);
		info.serverAddr.sin_port		= htons(_port);
		info.serAddrLen					= sizeof(info.serverAddr);
		memset(info.serverAddr.sin_zero, 0, 8);
		
		bind(info.socketID, (struct sockaddr*)&info.serverAddr, info.serAddrLen);
		
		cout<<"LISTEN AT PORT "<<ntohs(info.serverAddr.sin_port)<<"."<<endl;
		
		if(listen(info.socketID, _maxWaitQue) != 0)
		{
			throw("listen fail.");
		}
		
		unloginLiveTime.tv_sec = _loginTime;
		unloginLiveTime.tv_usec= 0;
		
		cout<<"SERVER RUNNING..."<<endl;
		
		//startup();
	}
	
	
	private:static void loginListenThread
	(
		socket_t		_connect,
		UsersManager*	_userMan
	)
	{
		int recvLength = 0;
		ZTouchDataHead head;
		if(!ZMessage::getHead(_connect, head, MSG_WAITALL))	close(_connect);
		if(head.getType() != ZTouchDataHead::LOGIN)
		{
			close(_connect);
			return;
		}
		
		#ifdef _DEBUG
			cout<<"loginListener recv a login request from socket id "<<_connect<<endl;
		#endif
		
		char *newUserName = new char[head.getLength()];
		
		recvLength = recv(_connect, newUserName, head.getLength(), MSG_WAITALL);
		#ifdef _DEBUG
			cout<<"username:"<<newUserName<<endl;
		#endif
		if(recvLength != head.getLength())
		{
			delete newUserName;
			close(_connect);
			return;
		}
		
		head.setLength(0);
		if(_userMan->login(_connect, string(newUserName)) == false)
		{
			head.setType(ZTouchDataHead::LOGINFAIL);
			send(_connect, &head, sizeof(head), MSG_WAITALL);
			close(_connect);
		}
		else
		{
			head.setType(ZTouchDataHead::LOGINSUCCEED);
			send(_connect, &head, sizeof(head), MSG_WAITALL);
			#ifdef _DEBUG
				cout<<"user on socket id "<<_connect<<" login succeed, name:"<<newUserName<<endl;
			#endif
		}
		
		delete newUserName;
	}
		
	private:static void connectListenThread
	(
		const ServerInfo* const	_serverInfo,
		UsersManager*			_userMan,
		struct timeval*			_liveTime//,	
		//mutex*				_concurrentLock
	)
	{
		cout<<"LOGIN MANAGER WORKING..."<<endl;
		thread*	eachNewConnect;
		
		for(socket_t aConnect = -1; _serverInfo->started;)
		{
			aConnect = accept(_serverInfo->socketID, NULL, 0);//
			cout<<"GET ACCEPTED CONNECTION SOCKET:"<<aConnect<<endl;
			if(aConnect != -1)
			{
				setsockopt(aConnect, SOL_SOCKET, SO_RCVTIMEO, (char*)_liveTime, sizeof(struct timeval));
				eachNewConnect = new thread(loginListenThread, aConnect, _userMan/*, _concurrentLock*/);
				eachNewConnect->detach();
			}
		}
		
		cout<<"LOGIN MANAGER WORK END"<<endl;
	}
	
	
	public:void startup(void)
	{
		if(info.startup())
		{
			//listenWorking = new thread(connectListenThread, &info, &usersMan, &unloginLiveTime/*, &concurrentLock*/);
			//TODO RUN THREADS
			connectListenThread(&info, &usersMan, &unloginLiveTime);
		}
		
	}
	
	public:static void closeWaitingConnectThreadOnPort(u_short port)
	{
		sockaddr_in tempAddr;
		socket_t tempSocket 		= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		tempAddr.sin_family 		= AF_INET;
		tempAddr.sin_addr.s_addr	= inet_addr("127.0.0.1");
		tempAddr.sin_port			= port;
		memset(tempAddr.sin_zero, 0, 8);
		
		if(connect(tempSocket, (struct sockaddr*)&tempAddr, sizeof(tempAddr)) == 0)
		{
			ZTouchDataHead tempHead(0, ZTouchDataHead::TERMI);
			send(tempSocket, &tempHead, sizeof(tempHead), MSG_DONTWAIT);
		}
		
		close(tempSocket);
	}
	/*
	public:static void closeWaitingRecvThreadOnPort(u_short port)
	{
		sockaddr_in tempAddr;
		socket_t tempSocket 		= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		tempAddr.sin_family 		= AF_INET;
		tempAddr.sin_addr.s_addr	= inet_addr("127.0.0.1");
		tempAddr.sin_port			= port;
		memset(tempAddr.sin_zero, 0, 8);
		
		if(connect(tempSocket, (struct sockaddr*)&tempAddr, sizeof(tempAddr)) == 0)
		{
			ZTouchDataHead tempHead = {0, ZTouchDataHead::TERMI};
			send(tempSocket, &tempHead, sizeof(tempHead), MSG_DONTWAIT);
		}
		
		close(tempSocket);
	}
	*/
	public:void termi(void)
	{
		if(info.started)
		{
			info.termi();
			
			closeWaitingConnectThreadOnPort(info.serverAddr.sin_port);
			
			listenWorking->join();
			close(info.socketID);
			
			cout<<"SERVER TERMINATED"<<endl;
		}	else cout<<"SERVER NOT RUNNING"<<endl;
	}
	
	public:~ZTouchTCPServer(void)
	{
		termi();
	}
};
};
#endif
